using System;
using System.Collections.Generic;
using System.Linq;
using bbv.DomainDrivenDesign.Interfaces;

namespace O1.Social.Newsgator.Repository.LinqToSql
{
    public class ReadOnlyRepository<TEntity, TDataTransferObject, TId> : IReadOnlyRepository<TDataTransferObject, TId>
        where TEntity : class, new()
        where TDataTransferObject : class, IDataTransferObject<TId>
        where TId : IComparable<TId>
    {
        #region Protected properties
        /// <summary>
        ///   The context which the repository uses.
        /// </summary>
        protected virtual IReadOnlyConverter<TDataTransferObject, TEntity> Converter { get; private set; }
        protected Func<IWorkspace> GetWorkspace { get; private set; }
        #endregion

        #region Constructor
        public ReadOnlyRepository(IReadOnlyConverter<TDataTransferObject, TEntity> converter) : this(new LinqToSqlWorkspaceProvider(), converter) { }

        public ReadOnlyRepository(IWorkspaceProvider workspaceCreator, IReadOnlyConverter<TDataTransferObject, TEntity> converter)
        {
            GetWorkspace = () =>
            {
                var ws = workspaceCreator.Workspace;
                DefaultIncludes(ws);
                return ws;
            };
            Converter = converter;
        }
        #endregion

        #region IReadOnlyRepository<TEntity,TDataTransferObject,TId> Members
        protected virtual void DefaultIncludes(IWorkspace workspace) { }
        public virtual TDataTransferObject GetById(TId id)
        {
            using (var context = GetWorkspace())
            {
                var item = context.GetById<TEntity, TId>(id);
                return item != null ? Converter.Convert(item) : null;
            }
        }
        /// <summary>
        ///   Find a particular entity or entity set by a given specification.
        /// </summary>
        /// <param name="specification">The specification.</param>
        /// <param name="context"></param>
        /// <returns>
        ///   An enumerable of type
        ///   <see cref="T:System.Collections.Generic.IEnumerable`1" />
        ///   .
        /// </returns>
        internal IQueryable<TEntity> FindBySpecification(IWorkspace context, ISpecification<TEntity> specification)
        {
            return context.CreateQuery<TEntity>().Where(specification.Predicate);
        }
        public IList<TDataTransferObject> FindBySpecification(ISpec<TDataTransferObject> specification)
        {
            return FindBySpecification(specification, _ => _.Id);
        }
        public IList<TDataTransferObject> FindBySpecification<TKey>(ISpec<TDataTransferObject> specification, Func<TDataTransferObject, TKey> orderBy)
        {
            var spec = specification as Spec<TDataTransferObject, TEntity>;
            if (spec == null) throw new ArgumentException("specification");

            using (var context = GetWorkspace())
            {
                return FindBySpecification(context, spec.InnerSpec).Select(Converter.Convert).OrderBy(orderBy).ToList();
            }
        }
        public int FindBySpecificationCount(ISpec<TDataTransferObject> specification)
        {
            var spec = specification as Spec<TDataTransferObject, TEntity>;
            if (spec == null) throw new ArgumentException("specification");

            using (var context = GetWorkspace())
            {
                return FindBySpecification(context, spec.InnerSpec).Count();
            }
        }
        public TDataTransferObject FindBySpecification<TKey>(ISpec<TDataTransferObject> specification, Func<TDataTransferObject, TKey> orderBy, Materialisation materialisation)
        {
            var spec = specification as Spec<TDataTransferObject, TEntity>;
            if (spec == null) throw new ArgumentException("specification");

            using (var context = GetWorkspace())
            {
                var result = FindBySpecification(context, spec.InnerSpec).Select(Converter.Convert).OrderBy(orderBy);
                switch (materialisation)
                {
                    case Materialisation.Last:
                        return result.Last();
                    case Materialisation.LastOrDefault:
                        return result.LastOrDefault();
                    case Materialisation.Single:
                        return result.Single();
                    case Materialisation.SingleOrDefault:
                        return result.SingleOrDefault();
                    case Materialisation.First:
                        return result.First();
                    case Materialisation.FirstOrDefault:
                        return result.FirstOrDefault();
                    default:
                        throw new ArgumentOutOfRangeException("materialisation");
                }
            }
        }
        /// <summary></summary>
        /// <returns></returns>
        public virtual List<TDataTransferObject> GetAll()
        {
            using (var context = GetWorkspace())
            {
                return context.CreateQuery<TEntity>().Select(Converter.Convert).ToList();
            }
        }
        #endregion
    }

    public interface IWorkspaceProvider
    {
        IWorkspace Workspace { get; }
    }
}